

#define clockpin 13 // CI
#define enablepin 10 // EI
#define latchpin 9 // LI
#define datapin 11 // DI

#define LIGHT_THRESHOLD_POT_PIN 5
#define LIGHT_SENSOR_PIN 0
#define PIR_PIN 2

#define NumLEDs 24
#define FADE_IN 1
#define FADE_OUT 2
#define FADE_NONE 0
#define FADE_TO_RED 3

#define RED_IDLE 0
#define GREEN_IDLE 1
#define BLUE_IDLE 2

#define RED_START 800
#define GREEN_START -250
#define BLUE_START 0

#define RED_MIN 0
#define GREEN_MIN -250
#define BLUE_MIN 0

#define RED_MAX 1000
#define BLUE_MAX 1000
#define GREEN_MAX BLUE_MAX + GREEN_START

#define MAX_VALUE 600
#define DISTANCE_SCALE MAX_VALUE / NumLEDs

#define SHUTDOWN_LENGTH 50

#define DELAY_RATE 6

// rizzz, variables
int redFadeValue;
int greenFadeValue;
int blueFadeValue;
int redFadeControl;
int greenFadeControl;
int blueFadeControl;
int idleColor;

// LED array
int LEDChannels[NumLEDs][3] = {0};
int SB_CommandMode;
int SB_RedCommand;
int SB_GreenCommand;
int SB_BlueCommand;

boolean lastMotionDetected = false;

// fade-idle-to-red controls
boolean fadeToRedTimerControl = false;
unsigned long fadeToRedTimer = 0;

// debug timer
unsigned long debugTimer = millis();

// sleep state
boolean sleep = true;

void loop() {
  if (isShutdown()) {
    goToSleep();
  } else {
    runLoop();
  }
}

void initColors() {
  redFadeValue = RED_START;
  greenFadeValue = GREEN_START;
  blueFadeValue = BLUE_START;

  redFadeControl = FADE_NONE;
  greenFadeControl = FADE_OUT;
  blueFadeControl = FADE_OUT;

  idleColor = RED_IDLE;
}

void goToSleep() {
  if (!sleep) {
    zeroColors();
    lastMotionDetected = false;
    fadeToRedTimerControl = false;
    fadeToRedTimer = 0;
    redFadeControl = 0;
    greenFadeControl = 0;
    blueFadeControl = 0;
    sendColorsToMegabrites();
    sleep = true;
  }
  delay(SHUTDOWN_LENGTH);
}

void runLoop() {
  handleWakeUp();
  handleTimer();
  handleMotionDetection();
  handleFadeControls();
  adjustIdleColor();
  limitColors();  
  sendColorsToMegabrites();
  delay(DELAY_RATE);
}

void handleWakeUp() {
  if (sleep) {
    sleep = false;
    initColors();
  }
}

boolean isShutdown() {
  /*
  int thresholdPotValue = map(analogRead(LIGHT_THRESHOLD_POT_PIN), 0, 1023, 0, 1000);
  int photoResistorValue = analogRead(LIGHT_SENSOR_PIN);
  Serial.print(thresholdPotValue);
  Serial.print(" > ");
  Serial.println(photoResistorValue);
  return photoResistorValue > thresholdPotValue;
  */
  return false;
}

void handleTimer() {
}

void adjustIdleColor() {
  if (redFadeControl == FADE_NONE && blueFadeValue >= BLUE_MAX - 200 && blueFadeControl == FADE_IN) {
    redFadeControl = FADE_IN;
    blueFadeControl = FADE_NONE;
    startFadeToRedTimer();
  } else if (blueFadeControl == FADE_NONE && greenFadeValue >= GREEN_MAX - 200 && greenFadeControl == FADE_IN) {
    blueFadeControl = FADE_IN;
    greenFadeControl = FADE_NONE;
    startFadeToRedTimer();
  } else if (greenFadeControl == FADE_NONE && redFadeValue >= RED_MAX - 200 && redFadeControl == FADE_IN) {
    greenFadeControl = FADE_IN;
    redFadeControl = FADE_NONE;
    startFadeToRedTimer();
  }
  
  // is idle, but not red
  if (isFadeToRedDelay()) {
    if (redFadeValue <= 1 && (blueFadeValue <= 1 || greenFadeValue <= 1)) {
      if (redFadeControl == FADE_OUT) {
        redFadeControl = FADE_TO_RED;
        blueFadeControl = FADE_OUT;
        greenFadeControl = FADE_OUT;
        fadeToRedTimer = millis();
        fadeToRedTimerControl = false;
      }
    }
  }
}

void debug() {
  if (millis() - debugTimer < 500) {
    return;
  }
  debugTimer = millis();
 Serial.print(millis() - fadeToRedTimer);
 Serial.print("isFadeToRedDelay: ");
 if (isFadeToRedDelay()) {
   Serial.print("true");
 } else {
   Serial.print("false");
 }
 Serial.print(", fadeToRedTimerControl: ");
 if (fadeToRedTimerControl) {
   Serial.print("true"); 
 } else {
   Serial.print("false"); 
 }
 Serial.print(", rfc: ");
 Serial.print(redFadeControl);
 Serial.print(", gfc: ");
 Serial.print(greenFadeControl);
 Serial.print(", bfc: ");
 Serial.print(blueFadeControl);
 Serial.print(", red: ");
 Serial.print(redFadeValue);
 Serial.print(", green: ");
 Serial.print(greenFadeValue);
 Serial.print(", blue: ");
 Serial.print(blueFadeValue);
 Serial.println("");
}

void startFadeToRedTimer() {
    fadeToRedTimer = millis();
    fadeToRedTimerControl = true;
}

boolean isFadeToRedDelay() {
  return fadeToRedTimerControl && millis() - fadeToRedTimer > 60000;
}

void limitColors() {
  if (redFadeValue < RED_MIN) {
    redFadeValue = RED_MIN;
  } else if (redFadeValue > RED_MAX) {
    redFadeValue = RED_MAX;
  }

  if (greenFadeValue < GREEN_MIN) {
    greenFadeValue = GREEN_MIN;
  } else if (greenFadeValue > GREEN_MAX) {
    greenFadeValue = GREEN_MAX;
  }
  
  if (blueFadeValue < BLUE_MIN) {
    blueFadeValue = BLUE_MIN;
  } else if (blueFadeValue > BLUE_MAX) {
    blueFadeValue = BLUE_MAX;
  }
}

void handleMotionDetection() {
  boolean currentMotionDetected = isMotion();
  if (currentMotionDetected != lastMotionDetected) {
    lastMotionDetected = currentMotionDetected;
    redFadeControl = swapFade(redFadeControl);
    greenFadeControl = swapFade(greenFadeControl);
    blueFadeControl = swapFade(blueFadeControl);
  }
}

int swapFade(int fadeControl) {
    if (fadeControl == FADE_IN) {
      return FADE_OUT;
    } else if (fadeControl == FADE_OUT) {
      return FADE_IN;
    } else if (fadeControl == FADE_TO_RED) {
      return FADE_TO_RED;
    }  
    
    return FADE_NONE;
}

void handleFadeControls() {
  if (redFadeControl == FADE_IN) {
    redFadeValue++;
  } else if (redFadeControl == FADE_OUT) {
    redFadeValue--;
  } else if (redFadeControl == FADE_TO_RED) {
    if (redFadeValue < RED_START) {
      redFadeValue++;
    } else {
      redFadeControl = FADE_NONE;
    }
  }

  if (greenFadeControl == FADE_IN) {
    greenFadeValue++;
  } else if (greenFadeControl == FADE_OUT) {
    greenFadeValue--;
  }

  if (blueFadeControl == FADE_IN) {
    blueFadeValue++;
  } else if (blueFadeControl == FADE_OUT) {
    blueFadeValue--;
  }  
}

int normalize(int blah) {
  if (blah < 0) {
    return 0;
  } else if (blah > 1023) {
    return 1023;
  }
  return blah;
}


boolean isMotion() {
   if (digitalRead(PIR_PIN) == HIGH) {
     return true;
   }
   return false;
}

void sendColorsToMegabrites() {
  writeColorsToArray();
  WriteLEDArray();
}

void writeColorsToArray() {
    for (int i = 0; i < NumLEDs; i++) {
    LEDChannels[i][0] = getRed(i, redFadeValue);
    LEDChannels[i][1] = getGreen(i, greenFadeValue);
    LEDChannels[i][2] = getBlue(i, blueFadeValue);
  }
}

int getRed(int led, int intensity) {
  return getSimpleDistanceScaleValue(led, intensity);
}

int getBlue(int led, int intensity) {
  return getSimpleDistanceScaleValue(led, intensity);
}

int getGreen(int led, int intensity) {
  return getSimpleDistanceScaleValue(led, intensity);
}

int getSimpleDistanceScaleValue(int led, int intensity) {
  intensity -= led * DISTANCE_SCALE;
  return intensity;
}  
void zeroColors() {
  redFadeValue = 0;
  greenFadeValue = 0;
  blueFadeValue = 0;
}

/* Return a visually-scaled value for the linear input value. Hand-tuned.
 */
int getScaledValue(int value) {
  if (value < 100) {
    if (value < 0) {
      value = 0;
    }
    return map(value, 0, 100, 0, 10);
  } else if (value < 200) {
    return map(value, 100, 200, 10, 100);
  } else if (value < 300) {
    return map(value, 200, 300, 100, 400);
  } else {
    if (value > MAX_VALUE) {
      value = MAX_VALUE;
    }
    return map(value, 300, MAX_VALUE, 400, 1023);
  }
}

void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT);
  digitalWrite(2, LOW);

   pinMode(datapin, OUTPUT);
   pinMode(latchpin, OUTPUT);
   pinMode(enablepin, OUTPUT);
   pinMode(clockpin, OUTPUT);
   SPCR = (1<<SPE)|(1<<MSTR)|(0<<SPR1)|(0<<SPR0);
   digitalWrite(latchpin, LOW);
   digitalWrite(enablepin, LOW);
   
   handleWakeUp();
}

void SB_SendPacket() {
 
    if (SB_CommandMode == B01) {
     SB_RedCommand = 120;
     SB_GreenCommand = 100;
     SB_BlueCommand = 100;
    }
 
    SPDR = SB_CommandMode << 6 | SB_BlueCommand>>4;
    while(!(SPSR & (1<<SPIF)));
    SPDR = SB_BlueCommand<<4 | SB_RedCommand>>6;
    while(!(SPSR & (1<<SPIF)));
    SPDR = SB_RedCommand << 2 | SB_GreenCommand>>8;
    while(!(SPSR & (1<<SPIF)));
    SPDR = SB_GreenCommand;
    while(!(SPSR & (1<<SPIF)));
}
 
void WriteLEDArray() {
    SB_CommandMode = B00; // Write to PWM control registers
    for (int h = 0;h<NumLEDs;h++) {
	  SB_RedCommand = normalize(getScaledValue(LEDChannels[h][0]));
	  SB_GreenCommand = normalize(getScaledValue(LEDChannels[h][1]));
	  SB_BlueCommand = normalize(getScaledValue(LEDChannels[h][2]));
	  SB_SendPacket();
    }
 
    delayMicroseconds(15);
    digitalWrite(latchpin,HIGH); // latch data into registers
    delayMicroseconds(15);
    digitalWrite(latchpin,LOW);
 
    SB_CommandMode = B01; // Write to current control registers
    for (int z = 0; z < NumLEDs; z++) SB_SendPacket();
    delayMicroseconds(15);
    digitalWrite(latchpin,HIGH); // latch data into registers
    delayMicroseconds(15);
    digitalWrite(latchpin,LOW);
}

